#include "EdgeGenerator.h"
#include <osg/TriangleLinePointIndexFunctor>
#include "OCMain/Plane.h"

namespace OCPP
{
    namespace Cesium
    {
        inline String genKey(osg::Vec3 v1, osg::Vec3 v2)
        {
            String k1 = Stringify() << v1.x() << " " << v1.y() << " " << StringConverter::formatDoubletoString(v1.z());
            String k2 = Stringify() << v2.x() << " " << v2.y() << " " << StringConverter::formatDoubletoString(v2.z());
            return v1.x() < v2.x() ? (k1 + " " + k2) : (k2 + " " + k1);
        }

        class EdgeCollector
        {
        public:
            inline void operator()(unsigned int p1)
            {

            }

            inline void operator()(unsigned int p1, unsigned int p2)
            {
                osg::Vec3 v1 = mPositions->at(p1);
                osg::Vec3 v2 = mPositions->at(p2);
                mEdgeGenerator->addEdge(v1, v2);
            }

            inline void operator()(unsigned int p1, unsigned int p2, unsigned int p3)
            {
                osg::Vec3 v1 = mPositions->at(p1);
                osg::Vec3 v2 = mPositions->at(p2);
                osg::Vec3 v3 = mPositions->at(p3);

                if (mFindEdgeInPrimitive)
                {
                    StringMap& edges = mEdgeGenerator->mEdges;
                    osg::Vec3Array* pos = mPositions;

                    auto findEdge = [&pos, &edges](unsigned int p1, unsigned int p2, unsigned int p3)
                    {
                        if (p1 > pos->size())
                        {
                            std::cout << p1 << "p1 error";
                        }

                        osg::Vec3 v1 = pos->at(p1);
                        osg::Vec3 v2 = pos->at(p2);
                        osg::Vec3 v3 = pos->at(p3);
                        String k = genKey(v1, v2);
                        auto it = edges.find(k);
                        if (it == edges.end())
                        {
                            String v = Stringify() << p1 << " " << p2 << " " << p3;
                            StringVector sv = StringUtil::split(v);
                            edges[k] = v;
                        }
                        else if (!it->second.empty())
                        {
                            StringVector sv = StringUtil::split(it->second);
                            if (sv.size() == 3)
                            {
                                Vector3 p333 = Vec3ToVector3(pos->at(p3));
                                osg::Vec3 v11 = pos->at(StringConverter::parseUnsignedInt(sv[0]));
                                osg::Vec3 v22 = pos->at(StringConverter::parseUnsignedInt(sv[1]));
                                osg::Vec3 v33 = pos->at(StringConverter::parseUnsignedInt(sv[2]));
                                OCPP::Plane plane(Vec3ToVector3(v11), Vec3ToVector3(v22), Vec3ToVector3(v33));
                                if (Math::floatEqual(plane.getDistance(p333), 0.0, 1e-6))
                                {
                                    edges[k] = "";
                                }
                            }
                        }
                    };

                    findEdge(p1, p2, p3);
                    findEdge(p2, p3, p1);
                    findEdge(p1, p3, p2);

                    findEdge(p2, p1, p3);
                    findEdge(p3, p2, p1);
                    findEdge(p3, p1, p2);
                }
                else
                {
                    if (mEdgeGenerator->isEdge(v1, v2))
                    {
                        mLineSet->addElement(p1);
                        mLineSet->addElement(p2);
                    }
                    if (mEdgeGenerator->isEdge(v2, v3))
                    {

                        mLineSet->addElement(p2);
                        mLineSet->addElement(p3);
                    }
                    if (mEdgeGenerator->isEdge(v1, v3))
                    {
                        mLineSet->addElement(p1);
                        mLineSet->addElement(p3);
                    }
                }
            }
            bool                                mFindEdgeInPrimitive = false;
            EdgeGenerator*                      mEdgeGenerator;
            osg::Vec3Array*                     mPositions;
            osg::ref_ptr<osg::DrawElementsUInt> mLineSet;
        };


        typedef osg::TriangleLinePointIndexFunctor<EdgeCollector> EdgeCollectorFunctor;

        void EdgeGenerator::addEdge(osg::Vec3 p1, osg::Vec3 p2)
        {
            String k = genKey(p1, p2);
            mEdges[k] = "0";
        }

        void EdgeGenerator::merge(osg::Geometry* geom, osg::PrimitiveSet* set)
        {
            osg::Vec3Array* positions = static_cast<osg::Vec3Array*>(geom->getVertexArray());
            EdgeCollectorFunctor t;
            t.mEdgeGenerator = this;
            t.mPositions = positions;
            set->accept(t);
        }

        bool EdgeGenerator::isEdge(osg::Vec3 p1, osg::Vec3 p2)
        {
            String k = genKey(p1, p2);
            StringMap::iterator it = mEdges.find(k);
            bool ret =  it != mEdges.end();
            if (ret)
            {
                ret = !it->second.empty();
                mEdges.erase(it);
            }
            return ret;
        }


        void EdgeGenerator::genEdgeSet(BatchGeodeList& mBatchGeodeList)
        {
            uint32 total = 0;
            std::cout << ",edgeNum:(";
            StringMap edgesCopy = mEdges;
            for (const auto geode : mBatchGeodeList)
            {
                mEdges = edgesCopy;
                EdgeCollectorFunctor t;
                t.mLineSet = new osg::DrawElementsUInt(osg::PrimitiveSet::LINES);
                t.mEdgeGenerator = this;
                t.mPositions = geode->positions();
                if(Cesium::gltfConfig::genenrateLineWithTriangle)
                {
                    t.mFindEdgeInPrimitive = true;
                    geode->primitive()->accept(t);
                }
                t.mFindEdgeInPrimitive = false;
                geode->primitive()->accept(t);

                uint32 edgeNum = t.mLineSet->getNumIndices();
                std::cout << edgeNum << ",";
                if (edgeNum > 0)
                {
                    geode->setEdgePrimitive(t.mLineSet);
                }
                total += edgeNum;
            }
            std::cout << "Total:" << total << ")" << std::endl;
        }
    }
}

